/*
* Copyright (c) 2017 Sprint
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef __FD_H
#define __FD_H

#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
#include <time.h>

#include <stdexcept>
#include <string>
#include <list>
#include <map>

#include "freeDiameter/freeDiameter-host.h"
#include "freeDiameter/libfdcore.h"
#include "freeDiameter/libfdproto.h"

#include "stime.h"
#include "stimer.h"
#include "sutility.h"

class FDException : public std::runtime_error
{
public:
   FDException(const char *m) : std::runtime_error(m) {}
   FDException(const std::string &m) : std::runtime_error(m) {}
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

template<class T>
class FDBuffer
{
public:
   FDBuffer(size_t size) { msize = size; mbuf = new T[msize]; }
   ~FDBuffer() { if (mbuf) delete [] mbuf; }
   T *get() { return mbuf; }
private:
   FDBuffer();
   size_t msize;
   T *mbuf;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// STARTUP, SHUTDOWN AND ADVERTISING SUPPORT FOR AN APPLICATION
//
//    Initialization of the freeDiameter library is achieved
//    by performing the following operations.
//
//    1. Create an instance of FDEngine that will exist for the
//       duration of the applciation.
//    2. Tell the freeDiameter library what configuration file to
//       use by calling FDEngine::setConfigFile().
//    3. Initialize the freeDiameter library by calling the
//       FDEngine::init() method.  This will only initialize the
//       library, it will not actually start communicating with other
//       diameter endpoints until the start() method is called.
//    4. Call FDEngine::advertiseSupport() for each supported diameter
//       application.  To populate the top level Auth-Application-Id or
//       Acct-Application-Id AVP in the Capabilities-Exchagne request or
//       answer, call the FDEngine::advertiseSupport() method that does
//       NOT accept a FDDictionaryEntryVendor object.  To populate the
//       the top level Vendor-Specific-Application-Id, use the
//       FDEngine::advertiseSupport() method that does accept a
//       FDDictionaryEntryVendor object.
//    5. After advertising support for all of the desired applications,
//       start freeDiameter by calling FDEngine::start().
//    6. At this point, freeDiameter is up and running.  The application
//       can wait for the freeDiameter engine to exit by calling
//       FDEngine::waitForShutdown().  This method will block until
//       freeDiameter has completed it's shutdown process.  The shutdown
//       process can be triggered by calling FDEngine::uninit() from any
//       thread or from a signal handler.
//
// SENDING A REQUEST AND RECEIVING AN ANSWER
//
//   *** SENDING A REQUEST ***
//         Derive new class from FDMessageRequest overriding the
//           processAnswer() virtual method. the processAnswer()
//           method will be called when the answer is received.
//
//           If using the interface specific objects generated by
//           fdtool, it may be necessary to derive a new object
//           from the generated message specific request object,
//           ie s6t::COIRreq, in order to add additional data that
//           may be required when processing the answer.
//
//      1. "new" the derived FDMessageRequest object
//      2. Populate the appropriate AVP's
//      3. Send the request using the send() method
//         *** DO NOT DELETE THE REQUEST OBJECT ***
//
//   *** RECEIVING AN ANSWER ***
//      1. The processAnswer() virtual method will be called
//           with the context of the original derived FDMessageRequest
//           object.
//      2. Inside the processAnswer() virtual method, Create the
//           appropriate FDMessageAnswer (or derived) object and process
//           the answer accordingly.
//      3. When the FDMessageAnswer object goes out of scope (or is
//           deleted), the underlying freeDiameter message will be
//           freed.
//      4. After returning from the processAnswer() method, the
//           FDMessageRequest internals will delete the associated
//           FDMessageRequest object.
//
// RECEIVING A REQUEST AND SENDING AN ANSWER
//
//      Unlike sending a request and receiving an answer where the
//      answer is processed by virtual method associated with the
//      request object, processAnswer(), to process an incoming
//      request, a command hander needs to be registered using the
//      FDApplication::registerHandler method.  If using the fdtool
//      generated interface specific objects, this can be done by
//      uncommenting the appropriate handler in the
//      Application::registerHandlers() method.  Then when an incoming
//      request is received the process() method associated with the
//      command object will be called to process the incoming request.
//
//      *** RECEIVING A REQUEST ***
//
//      1. The process() method of the previously registered command object
//         will be called by the framework.
//      2. Perform whatever processing is required to process the request.
//         If it is necessary to send a request to another application before
//         answering this request, make sure to save the FDMessageRequest*,
//         it will be needed to generate the answer.
//
//      *** SENDING AN ANSWER ***
//
//      1. Construct a FDMessageAnswer object by using the FDMessageRequest*
//         received in the command process() method.  The FDMessageAnswer can
//         be allocated on the stack, it does not need to be created on the heap
//         using "new".
//      2. After adding the necessary AVP's to the answer, send the answer message
//         by calling the FDMessageAnswer::send() method.
//      3. "delete" the FDMessageAnswer object if it was created using "new".
//
// ADDING AVP'S TO A MESSAGE
//
//    AVP's can be added to a message by calling any of the overloaded
//    FDMessage::add() methods.  To add an AVP using a basic data type, the
//    FDDictionaryEntryAVP that describes the AVP must also be passed in to
//    the FDMessage::add() method.  Additionally, a previously FDAvp object
//    can be added using the FDMessage::add() method.  If it is necessary to
//    copy an AVP from one message to another, the FDMessage::add() method
//    will accept any of the FDExtractor objects (FDExtractor, FDExtractorList,
//    FDExtractorAvp or FDExtractorAvpList).
//
// ADDING AVP'S TO A GROUPED AVP
//
//    First, create an FDAvp object using the appropriate FDDictionaryEntryAVP
//    object that describes the grouped AVP.  Then any AVP can be added to the
//    grouped AVP using the same overloads to the FDAvp::add() method that are
//    available in the FDMessage::add() methods.  Once all AVP's have been added
//    to the grouped AVP, add the grouped AVP to it's parent, another grouped AVP
//    or a message, using the add() method on the parent object.
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryApplication;
class FDDictionaryEntryVendor;

class FDEngine
{
public:
   FDEngine( const char *cfgfile = NULL );
   FDEngine( const std::string &cfgfile );
   ~FDEngine();

   bool init();
   bool start();
   void uninit( bool wait = true );

   void waitForShutdown();

   const std::string &getConfigFile() { return m_cfgfile; }
   const std::string &setConfigFile( const char *cf ) { return m_cfgfile = cf; }
   const std::string &setConfigFile( const std::string &cf ) { return setConfigFile( cf.c_str() ); }

   void advertiseSupport( FDDictionaryEntryApplication &app, int auth = 0, int acct = 0 );
   void advertiseSupport( FDDictionaryEntryApplication &app, FDDictionaryEntryVendor &vendor, int auth=0, int acct=0 );
private:
   std::string m_cfgfile;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntry
{
public:
   struct dictionary *getDictionary() const { return m_dict; }
   struct dict_object *getEntry() const { return m_de; }
   bool isValid() { return m_de != NULL; }

protected:
   FDDictionaryEntry();
   FDDictionaryEntry( const void *what, enum dict_object_type type, int criteria, struct dictionary *dict = NULL );

   void init( const void *what, enum dict_object_type type, int criteria, struct dictionary *dict = NULL );
   void init( struct dict_object *de, struct dictionary *dict = NULL );

   virtual ~FDDictionaryEntry();

private:

   struct dictionary *m_dict;
   struct dict_object *m_de;
   bool m_destroy;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryApplication : public FDDictionaryEntry
{
public:
   FDDictionaryEntryApplication( const char *name, struct dictionary *dict = NULL );

   const char *getName() const { return m_data.application_name; }
   application_id_t getId() const { return m_data.application_id; }

private:
   bool init();

   dict_application_data m_data;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryVendor : public FDDictionaryEntry
{
public:
   FDDictionaryEntryVendor( const char *name, struct dictionary *dict = NULL );
   FDDictionaryEntryVendor( vendor_id_t vendorid, struct dictionary *dict = NULL );
   FDDictionaryEntryVendor( const FDDictionaryEntryApplication &app, struct dictionary *dict = NULL );

   const char *getName() { return m_data.vendor_name; }
   vendor_id_t getId() { return m_data.vendor_id; }

private:
   bool init();

   dict_vendor_data m_data;
};

////////////////////////////////////////////////////////////////////////////////

enum DiameterDataType
{
   DDTUnknown,
   DDTOctetString,
   DDTI32,
   DDTI64,
   DDTU32,
   DDTU64,
   DDTF32,
   DDTF64,
   DDTGrouped,
   DDTAddress,
   DDTTime,
   DDTUTF8String,
   DDTDiameterIdentity,
   DDTDiameterURI,
   DDTEnumerated,
   DDTIPFilterRule
};

class FDDictionaryEntryAVP : public FDDictionaryEntry
{
public:

   FDDictionaryEntryAVP( const char *name, bool allVendors = false, struct dictionary *dict = NULL );
   FDDictionaryEntryAVP( const char *name, vendor_id_t vendorid, struct dictionary *dict = NULL );
   FDDictionaryEntryAVP( struct dict_object *de );

   bool isDerived() const { return m_isderived; }

   const char *getName() const { return m_basedata.avp_name; }
   vendor_id_t getVendorId() const { return m_basedata.avp_vendor; }
   avp_code_t getAvpCode() const { return m_basedata.avp_code; }
   DiameterDataType getDataType() const { return m_datatype; }

private:
   void getTypeInfo();
   
   std::string m_name;
   vendor_id_t m_vendorid;
   
   struct dict_avp_data m_basedata;
   struct dict_object *m_derivedtype;
   struct dict_type_data m_derivedtypedata;

   bool m_isderived;
   DiameterDataType m_datatype;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryCommand : public FDDictionaryEntry
{
public:
   FDDictionaryEntryCommand( const char *name, struct dictionary *dict = NULL );
   FDDictionaryEntryCommand( command_code_t cmdid, struct dictionary *dict = NULL );
   FDDictionaryEntryCommand( const FDDictionaryEntryCommand &req, struct dictionary *dict = NULL );

   bool isRequest() const { return ( m_data.cmd_flag_val & CMD_FLAG_REQUEST ) ? true : false; } 
   bool isAnswer() const { return ( m_data.cmd_flag_val & CMD_FLAG_REQUEST ) ? false : true; } 

   command_code_t getCommandCode() { return m_data.cmd_code; }

private:
   struct dict_cmd_data m_data;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDMessage;
class FDExtractor;
class FDExtractorList;
class FDExtractorAvp;
class FDExtractorAvpList;

class FDAvp
{
   friend FDMessage;

public:
   FDAvp( FDDictionaryEntryAVP &de, bool dedel = false );
   FDAvp( FDDictionaryEntryAVP &de, struct avp *a, bool dedel = false );

   ~FDAvp();

   FDAvp &add( FDAvp &avp );

   bool isValid() { return m_avp != NULL; }

   FDAvp &add( FDDictionaryEntryAVP &de, int32_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, int64_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, uint32_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, uint64_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, float v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, double v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, const char *v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, const std::string &v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, const char *v, size_t len ) { FDAvp avp( de ); avp.set( v, len ); return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, const uint8_t *v, size_t len ) { FDAvp avp( de ); avp.set( v, len ); return add( avp ); }
   FDAvp &add( FDDictionaryEntryAVP &de, const STime &v ) { FDAvp avp( de ); avp = v; return add( avp ); }

   FDAvp &operator=( int32_t v )              { return set( v ); }
   FDAvp &operator=( int64_t v )              { return set( v ); }
   FDAvp &operator=( uint32_t v )             { return set( v ); }
   FDAvp &operator=( uint64_t v )             { return set( v ); }
   FDAvp &operator=( float v )                { return set( v ); }
   FDAvp &operator=( double v )               { return set( v ); }
   FDAvp &operator=( const char *v )          { return set( v ); }
   FDAvp &operator=( const std::string &v )   { return set( v ); }
   FDAvp &operator=( const STime &v )         { return set( v ); }

   bool get( int32_t &v )                    { if ( m_avphdr == NULL ) return false; v = m_avphdr->avp_value->i32; return true; }
   bool get( uint32_t &v )                   { if ( m_avphdr == NULL ) return false; v = m_avphdr->avp_value->u32; return true; }
   bool get( uint64_t &v )                   { if ( m_avphdr == NULL ) return false; v = m_avphdr->avp_value->u64; return true; }
   bool get( float &v )                      { if ( m_avphdr == NULL ) return false; v = m_avphdr->avp_value->f32; return true; }
   bool get( double &v )                     { if ( m_avphdr == NULL ) return false; v = m_avphdr->avp_value->f64; return true; }
   bool get( int64_t &v );
   bool get( std::string &v );
   bool get( char *data, size_t &len );
   bool get( uint8_t *data, size_t &len );
   bool get( sSS &ss );
   bool get( STime &v );

   FDAvp &set( int32_t v )                    { m_value.i32 = v; assignValue(); return *this; }
   FDAvp &set( uint32_t v )                   { m_value.u32 = v; assignValue(); return *this; }
   FDAvp &set( uint64_t v )                   { m_value.u64 = v; assignValue(); return *this; }
   FDAvp &set( float v )                      { m_value.f32 = v; assignValue(); return *this; }
   FDAvp &set( double v )                     { m_value.f64 = v; assignValue(); return *this; }
   FDAvp &set( const char *v )                { return set( v, strlen( v ) ); }
   FDAvp &set( const uint8_t *v, size_t len ) { m_value.os.data = (uint8_t*)v; m_value.os.len = len; assignValue(); return *this; }
   FDAvp &set( const std::string &v )         { return set( v.c_str(), v.size() ); }
   FDAvp &set( int64_t v );
   FDAvp &set( const char *v, size_t len );
   FDAvp &set( const STime& v );

   FDAvp getNext( bool &found );
   FDAvp getChild( bool &found );

   struct avp *getAvp() { return m_avp; }
   union avp_value *getAvpValue() { return &m_value; }
   FDAvp &setAvp( struct avp *a );

   FDAvp &add( FDExtractor &e );
   FDAvp &add( FDExtractorList &el );
   FDAvp &add( FDExtractorAvp &ea );
   FDAvp &add( FDExtractorAvpList &eal );

   FDAvp &addJson( const char *json );
   FDAvp &addJson( const std::string &json ) { return addJson( json.c_str() ); }
   bool getJson( std::string &json );

   void dump();

   FDDictionaryEntryAVP &getDictionaryEntry() { return *m_de; }

protected:
   void addTo( msg_or_avp *reference );

private:
   void init();
   void assignValue();

   FDDictionaryEntryAVP *m_de;
   struct avp *m_avp;
   struct avp_hdr *m_avphdr;
   union avp_value m_value;
   FDBuffer<uint8_t> *m_buf;
   bool m_assigned;
   bool m_dedel;
};

class FDMessageRequest;
class FDMessageAnswer;

class FDMessage
{
public:
   FDDictionaryEntryCommand *getCommand() { return m_de; }

   FDMessage &add( FDAvp &avp ) { avp.addTo( m_msg ); return *this; }
   FDMessage &add( FDDictionaryEntryAVP &de, int32_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, int64_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, uint32_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, uint64_t v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, float v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, double v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, const char *v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, const std::string &v ) { FDAvp avp( de ); avp = v; return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, const char *v, size_t len ) { FDAvp avp( de ); avp.set( v, len ); return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, const uint8_t *v, size_t len ) { FDAvp avp( de ); avp.set( v, len ); return add( avp ); }
   FDMessage &add( FDDictionaryEntryAVP &de, const STime &v ) { FDAvp avp( de ); avp = v; return add( avp ); }

   FDMessage &add( FDExtractor &e );
   FDMessage &add( FDExtractorList &el );
   FDMessage &add( FDExtractorAvp &ea );
   FDMessage &add( FDExtractorAvpList &eal );

   bool get( FDDictionaryEntryAVP &de, int32_t &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, int64_t &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, uint32_t &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, uint64_t &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, float &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, double &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, std::string &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }
   bool get( FDDictionaryEntryAVP &de, char *v, size_t &len ) { FDAvp avp = findAVP( de ); return avp.get( v, len ); }
   bool get( FDDictionaryEntryAVP &de, uint8_t *v, size_t &len ) { FDAvp avp = findAVP( de ); return avp.get( v, len ); }
   bool get( FDDictionaryEntryAVP &de, STime &v ) { FDAvp avp = findAVP( de ); return avp.get( v ); }

   FDAvp findAVP( FDDictionaryEntryAVP &de );
   FDAvp getFirstAVP( bool &found );

   void dump();

   bool isRequest() { return m_de->isRequest(); }
   bool isAnswer() { return m_de->isAnswer(); }

   struct msg *getMsg() { return m_msg; }

   void addOrigin();

   FDMessage &addJson( const char *json );
   FDMessage &addJson( const std::string &json ) { return addJson( json.c_str() ); }
   bool getJson( std::string &json );

protected:
   FDMessage( bool req2ans, FDDictionaryEntryCommand *de, struct msg *pmsg = NULL, bool dedel = false, bool msgdel = true );
   FDMessage( FDDictionaryEntryCommand *de, struct msg *pmsg = NULL, bool dedel = false );
   FDMessage( FDDictionaryEntryApplication *ade, FDDictionaryEntryCommand *cde, struct msg *pmsg = NULL, bool dedel = false );
   ~FDMessage();

   FDMessage &sendRequest( void (*anscb)(void*,struct msg**), FDMessageRequest &req );
   FDMessage &sendAnswer();
   
   void setMsgDelete( bool v ) { m_msgdel = v; }

private:
   FDMessage();

   FDDictionaryEntryCommand *m_de;
   bool m_dedel;
   struct dict_cmd_data m_basedata;
   bool m_msgdel;
   struct msg *m_msg;
};

class FDMessageAnswer : public FDMessage
{
   friend FDMessageRequest;

public:
   FDMessageAnswer( FDDictionaryEntryCommand *cmd, struct msg *pmsg ); // used to encapsulate a FD answer message
   FDMessageAnswer( FDMessageRequest *req ); // used to create and encapsulate a FD answer from a request

   FDMessageAnswer &send();
};

class FDMessageRequest : public FDMessage
{
   friend FDMessageAnswer;

public:
   FDMessageRequest( FDDictionaryEntryCommand *cde );
   FDMessageRequest( FDDictionaryEntryCommand *cde, struct msg *pmsg );
   FDMessageRequest( FDDictionaryEntryApplication *ade, FDDictionaryEntryCommand *cde );
   virtual ~FDMessageRequest();

   FDMessageRequest &send();

   virtual void processAnswer( FDMessageAnswer &ans );

protected:
   STimerElapsed m_timer;

private:
   static void anscb( void * data, struct msg ** pmsg );

};

class FDCommand
{
public:
   FDDictionaryEntryCommand &getDictionaryEntry() { return m_de; }

   virtual bool isRequest() = 0;
   bool isAnswer() { return !isRequest(); }

protected:
   FDCommand( FDDictionaryEntryCommand &de );

private:
   FDCommand();

   FDDictionaryEntryCommand &m_de;
};

class FDCommandRequest : public FDCommand
{
public:
   FDCommandRequest( FDDictionaryEntryCommand &de );

   bool isRequest() { return true; }

   virtual int process( FDMessageRequest *req ) = 0;
};

#define FDISREQUEST(hdr) ((hdr->msg_flags & CMD_FLAG_REQUEST) == CMD_FLAG_REQUEST)
#define FDISANSWER(hdr) ((hdr->msg_flags & CMD_FLAG_REQUEST) != CMD_FLAG_REQUEST)

class FDApplication
{
public:
   FDApplication( FDDictionaryEntryApplication *de );
   ~FDApplication();

   FDDictionaryEntryApplication &getDictionaryEntry() { return *m_de; }

   FDApplication &registerHandler( FDCommandRequest &cmd );

protected:
   FDApplication() : m_de( NULL ) {}
   FDDictionaryEntryApplication *setDictionaryEntry( FDDictionaryEntryApplication *de ) { return m_de = de; }

private:

   static int commandcb( struct msg **m, struct avp *avp, struct session *session, void *data, enum disp_action *action );

   FDDictionaryEntryApplication *m_de;
   std::list<FDCommandRequest*> m_cmds;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDSession
{
public:
   FDSession();
   ~FDSession();

   const std::string &getSessionId();
   void addSessionId( FDMessage &msg, FDDictionaryEntryAVP &deSessionId );

private:
   void init();

   struct session *m_session;
   std::string m_sid;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

enum FDPeerState
{
   PSInvalid = -1,
   PSNew = 0,
   PSOpen,
   PSClosed,
   PSClosing,
   PSWaitCnxAck,
   PSWaitCnxAckElec,
   PSWaitCEA,
   PSOpenHandshake,
   PSSuspect,
   PSReOpen,
   PSOpenNew,
   PSClosingGrace,
   PSZombie
};

class FDPeer
{
public:
   FDPeer();
   FDPeer( DiamId_t diamid, uint16_t port = 3868 );
   FDPeer( const std::string &diamid, uint16_t port = 3868 );

   const std::string &getDiameterId() { return m_diamid; }
   FDPeer &setDiameterId( DiamId_t diamid ) { m_diamid = diamid; return *this; }

   uint16_t getPort() { return m_port; }
   FDPeer &setPort( uint16_t v ) { m_port = v; return *this; }

   const std::string &getDestinationHost() const { return m_diamid; }
   const std::string &getDestinationRealm() const { return m_destrealm; }

   const std::string &setDestinationIp( const char *ip ) { m_destip = ip; return getDestinationIp(); }
   const std::string &setDestinationIp( const std::string &ip ) { m_destip = ip; return getDestinationIp(); }
   const std::string &getDestinationIp() const { return m_destip; }

   FDPeerState getState();

   bool isOpen() { return getState() == PSOpen; }

   void add();

private:
   void init();
   static void peercb( struct peer_info *pi, void *data );

   std::string m_diamid;
   std::string m_destrealm;
   std::string m_destip;
   uint16_t m_port;
   struct peer_hdr *m_peer;
};

class FDPeerList : public std::list<FDPeer*>
{
public:
   FDPeerList();
   ~FDPeerList();

   bool isPeerOpen();

   FDPeer *getOpenPeer();
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDExtractorAvp;

enum eFDExtractorType
{
   etAvp,
   etAvpList,
   etExtractor,
   etExtractorList
};

class FDExtractorBase
{
public:
   FDExtractorBase( FDDictionaryEntryAVP *de )
      : m_de( de ),
        m_idx( -1 ),
        m_resolved( false )
   {
   }

   virtual ~FDExtractorBase()
   {
   }

   virtual eFDExtractorType getExtractorType() = 0;

   int getIndex() { return m_idx; }
   int setIndex( int idx ) { return m_idx = idx; }

   bool getResolved() { return m_resolved; }
   bool setResolved( bool resolved = true ) { return m_resolved = resolved; }

   bool exists() { return m_idx != -1; }

   FDDictionaryEntryAVP *getDictionaryEntry() { return m_de; }

private:
   FDDictionaryEntryAVP *m_de;
   int m_idx;
   bool m_resolved;
};

class FDExtractorKey
{
public:
   FDExtractorKey()
      : m_vndid( 0 ),
        m_avpcode( 0 )
   {
   }

   FDExtractorKey( vendor_id_t v, avp_code_t a )
      : m_vndid( v ),
        m_avpcode( a )
   {
   }

   FDExtractorKey( const FDExtractorKey &k )
      : m_vndid( k.m_vndid ),
        m_avpcode( k.m_avpcode )
   {
   }

   virtual ~FDExtractorKey()
   {
   }

   bool operator <(const FDExtractorKey &rval ) const
   {
      return
         m_vndid < rval.m_vndid ? true :
         m_vndid > rval.m_vndid ? false : m_avpcode < rval.m_avpcode;
   }

   vendor_id_t getVendor() { return m_vndid; }
   vendor_id_t setVendor( vendor_id_t v ) { return m_vndid = v; }

   avp_code_t getAvpCode() { return m_avpcode; }
   avp_code_t setAvpCode( avp_code_t a ) { return m_avpcode = a; }

private:
   vendor_id_t m_vndid;
   avp_code_t m_avpcode;
};

class FDExtractor : public FDExtractorBase
{
   friend FDExtractorList;
   friend FDExtractorAvp;
   friend FDExtractorAvpList;

public:
   FDExtractor();
   FDExtractor( FDMessage &msg );
   FDExtractor( FDDictionaryEntryCommand &de );
   FDExtractor( FDExtractor &parent, FDDictionaryEntryAVP &de );
   virtual ~FDExtractor();

   eFDExtractorType getExtractorType() { return etExtractor; }

   void setReference( FDMessage &msg ) { m_reference = msg.getMsg(); }
   void setReference( FDAvp &avp )     { m_reference = avp.getAvp(); }
   void setReference( msg_or_avp *m )  { m_reference = m; }

   FDExtractor *getParent() { return m_parent; }
   msg_or_avp *getReference();

   void add( FDExtractorBase &base );

   bool exists( bool skipResolve = false );

   void dump();

   bool getJson( std::string &json );

protected:
   void resolve();

private:
   FDExtractor *m_parent;
   msg_or_avp *m_reference;
   std::map<FDExtractorKey,FDExtractorBase*> m_entries;
   int m_index;
};

class FDExtractorList : public FDExtractorBase
{
   friend FDExtractor;
   friend FDAvp;
   friend FDMessage;

public:
   FDExtractorList( FDExtractor &parent, FDDictionaryEntryAVP &de );
   virtual ~FDExtractorList();

   eFDExtractorType getExtractorType() { return etExtractorList; }

   FDExtractor &getParent() { return *m_parent; }

   virtual FDExtractor *createExtractor() = 0;

   void addExtractor( FDExtractor *e );

   bool exists();

   void dump();

protected:
   std::list<FDExtractor*> &getList();

private:
   FDExtractorList();

   FDExtractor *m_parent;
   std::list<FDExtractor*> m_list;
};

class FDExtractorAvp : public FDExtractorBase
{
public:
   FDExtractorAvp( FDExtractor &extractor, FDDictionaryEntryAVP &de, bool dedel = false );
   virtual ~FDExtractorAvp();

   eFDExtractorType getExtractorType() { return etAvp; }

   void setAvp( struct avp *a ) { m_avp.setAvp( a ); }
   struct avp *getAvp() { return m_avp.getAvp(); }
   union avp_value *getAvpValue() { return m_avp.getAvpValue(); }

   bool exists();

   bool get( int32_t &v )                    { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( uint32_t &v )                   { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( uint64_t &v )                   { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( float &v )                      { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( double &v )                     { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( int64_t &v )                    { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( std::string &v )                { if ( !exists() ) return false; return m_avp.get( v ); }
   bool get( char *data, size_t &len )       { if ( !exists() ) return false; return m_avp.get( data, len ); }
   bool get( uint8_t *data, size_t &len )    { if ( !exists() ) return false; return m_avp.get( data, len ); }
   bool get( sSS &ss )                       { if ( !exists() ) return false; return m_avp.get( ss ); }
   bool get( STime &t )                      { if ( !exists() ) return false; return m_avp.get( t ); }

   void dump();

   bool getJson( std::string &json );

private:
   FDExtractor &m_extractor;
   FDAvp m_avp;
};

class FDExtractorAvpList : public FDExtractorBase
{
public:
   FDExtractorAvpList( FDExtractor &extractor, FDDictionaryEntryAVP &de );
   virtual ~FDExtractorAvpList();

   eFDExtractorType getExtractorType() { return etAvpList; }

   std::list<FDExtractorAvp*> &getList();

   bool exists();

   void dump();

private:
   FDExtractorAvpList();

   FDExtractor *m_parent;
   std::list<FDExtractorAvp*> m_list;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDUtility
{
public:
   static void splitDiameterFQDN( std::string &fqdn, std::string &host, std::string &realm );
   static void splitDiameterFQDN( const char *fqdn, std::string &host, std::string &realm ) { std::string sfqdn( fqdn ); splitDiameterFQDN( sfqdn, host, realm ); }

   static size_t str2tbcd( const char *src, size_t srclen, uint8_t *dst, size_t dstlen );
   static size_t str2tbcd( const char *src, uint8_t *dst, size_t dstlen );
   static size_t str2tbcd( const std::string &src, uint8_t *dst, size_t dstlen );

   static size_t tbcd2str( uint8_t *src, size_t srclen, char *dst, size_t dstlen );
   static size_t tbcd2str( uint8_t *src, size_t srclen, std::string &dst );
};

#endif // #define __FD_H
